
package net.flowas.genericcrud.service.chain;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;
import com.github.dozermapper.core.metadata.ClassMappingMetadata;

import net.flowas.genericcrud.ExecuteContext;
import net.flowas.genericcrud.ResourceRegister;
import net.flowas.genericcrud.service.impl.RequestDispatcher;

/**
 * 通过dto对象和前台交互，并将dto转化为jpa的Entity对象.
 * 
 * @author Administrator
 */
public class DtoWrapeJpaHandler extends AbstractExecuteHandler implements ResourceRegister{
	private static final Logger LOG = LoggerFactory.getLogger(DtoWrapeJpaHandler.class);
	
	private RequestDispatcher repository;
	private Mapper mapper;
	private Map<Class<? extends Serializable>, Class<? extends Serializable>> dtoJpaMaps;
	private Map<Class<? extends Serializable>, Class<? extends Serializable>> jpaDtoMaps;

	private Map<String,URL> inputStreams = new HashMap<>();
	/**
	 * 实际处理将委托给JpaRepositoryHandler处理.
	 */	
	private ExecuteHandler delegate;
	
	/**
	 * 对于Dto类型的判定和处理，都要转换为Entity类型的判定和处理.
	 * 
	 * @param next 委托类
	 * @param repository 
	 */
	public DtoWrapeJpaHandler(ExecuteHandler next, RequestDispatcher repository) {
		super(next);
		this.delegate = next;		
		this.repository = repository;
		try {
			Enumeration<URL> urls = DtoWrapeJpaHandler.class.getClassLoader().getResources("META-INF/entityToDto-mapper.xml");
			if(urls.hasMoreElements()) {
				URL url = urls.nextElement();
				inputStreams.put("base", url);
				InputStream tmpIn = url.openStream();			
				parceMappings(Arrays.asList(tmpIn),null);
			}
		} catch (IOException e) {
			LOG.error("can not load base mappings!", e);
		}
		
	}

	@Override
	public void handleRequest(ExecuteContext req) {
		req.setModelClass(dtoJpaMaps.get(req.getModelClass()));
		switch (req.getAction()) {		
		case CREATE:
		case MERGE:
		case UPDATE:
			Object entity = mapper.map(req.getQueryOption().getExample(), req.getModelClass());
			req.getQueryOption().setExample(entity);
			break;
		default:
			break;
		}
		super.handleRequest(req);
		switch (req.getAction()) {
		case GET:
		case CREATE:
		case MERGE:
		case UPDATE:	
			if(req.getResult()!=null) {
				req.setResult(mapper.map(req.getResult(), jpaDtoMaps.get(req.getModelClass())));
			}
			break;
        case QUERY:
        	List result = (List) ((List) req.getResult()).stream().map(sourceObject -> mapper.map(sourceObject, jpaDtoMaps.get(req.getModelClass())))
			.collect(Collectors.toList());
        	req.setResult(result);
        	break;        
		default:
			break;
		}
	}
	
	
	
	private void parceMappings(List<InputStream> xmlMappings, ClassLoader classLoader) {
		DozerBeanMapperBuilder builder = DozerBeanMapperBuilder.create();
		for (InputStream inputStream : xmlMappings) {
			builder.withXmlMapping(()->inputStream);
		}
		mapper = builder.withClassLoader(classLoader).build();
		Collection<Class<? extends Serializable>> jpaTypes =  delegate.getHandledTypes();
		dtoJpaMaps = new HashMap<>();
		jpaDtoMaps = new HashMap<>();
		List<ClassMappingMetadata> classMappings = mapper.getMappingMetadata().getClassMappings();
		for (ClassMappingMetadata classMappingMetadata : classMappings) {
			Class<? extends Serializable> clasz1 = (Class<? extends Serializable>) classMappingMetadata
					.getSourceClass();
			Class<? extends Serializable> clasz2 = (Class<? extends Serializable>) classMappingMetadata
					.getDestinationClass();
			Class<? extends Serializable> jpaClass;
			Class<? extends Serializable> dtoClass;
			if (jpaTypes.contains(clasz1) && !jpaTypes.contains(clasz2)) {
				jpaClass = clasz1;
				dtoClass = clasz2;
			} else if (jpaTypes.contains(clasz2) && !jpaTypes.contains(clasz1)) {
				jpaClass = clasz2;
				dtoClass = clasz1;
			} else {
				LOG.warn("calss[{}] and class[{}] are not jpa+dto pair!", clasz1.getName(),clasz2.getName());
				continue;
			}
			if (!dtoJpaMaps.containsKey(dtoClass)) {
				dtoJpaMaps.put(dtoClass, jpaClass);
				jpaDtoMaps.put(jpaClass, dtoClass);
			}
		}
		this.repository.registHandler(this);
	}

	
	@Override
	public Collection<Class<? extends Serializable>> getHandledTypes() {
		return dtoJpaMaps.keySet();
	}

	public void addResource(URL url, ClassLoader classLoader) {
		String path = url.getPath();
		if(!inputStreams.containsKey(path)) {
			inputStreams.put(path, url);
			//TODO 每个url使用不同的ClassLoader，由于Dozer是一次性解析的，暂时没法设置多个ClassLoader
			parceMappings(inputStreams.values().stream().map(urlIner-> {
				try {
					return urlIner.openStream();
				} catch (IOException e) {
					LOG.error("failed to process mappings provided by["+path+"]!",e);
					return null;
				}
			}).collect(Collectors.toList()),classLoader);
		}
	}

	public void removeResource(URL url) {
		dtoJpaMaps.clear();
	}		
}
